﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.Schema;
using Automator.Runtime.Classes;
using Automator.Tasks.Core;

namespace Automator.Runtime
{
    /// <summary>
    /// Class that implements workflow runtime environment
    /// </summary>
    public sealed class Workflow: RuntimeEntity
    {
        #region Fields
        
        private string _internalName;
        private static int _workflowIndex = 0;
        
        #endregion
        
        #region Properties
        
        /// <summary>
        /// Workflow name, usually name of the script file
        /// </summary>
        public override string Name
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                if (!this.Id.Equals(Guid.Empty))
                    sb.Append(this.FileName);
                else
                {
                    if (!String.IsNullOrEmpty(FileName))
                        sb.Append(Path.GetFileName(FileName));
                    else if (!String.IsNullOrEmpty(_internalName))
                        sb.Append(_internalName);
                    else sb.Append("New Workflow");
                }
                if (this.Modified)
                    sb.Append("*");
                return sb.ToString();
            }
        }
        
        public TaskCollection Tasks { get; private set; }
        
        public Solution Parent { get; internal set; }
        
        #endregion
        
        #region Methods
        
        public Workflow():
            base()
        {
            this.Tasks = new TaskCollection();
            
            _workflowIndex++;
            this._internalName = String.Format("Workflow{0}", _workflowIndex);
            if (XmlValidation.WorkflowSchema != null)
                this.XSDSchema = XmlValidation.WorkflowSchema;
            else
                this.Log.AddLog(LogMessageType.Warning, "Could not load Workflow XSD schema");
            this.FileFormatCommentString = "Automator Workflow Script File, Format Version ";
        }
        
        /// <summary>
        /// Executes the whole workflow, resetting its state
        /// </summary>
        public override void Run()
        {
            Log.AddLog(LogMessageType.Info, "Starting the workflow");
            this.State = EntityExecutingState.Executing;
            WorkflowExecutionContext context = new WorkflowExecutionContext();
            foreach (Task task in Tasks)
                RunTask(task, context);
            LogMessageType mesType = (this.State == EntityExecutingState.Error) ? LogMessageType.Warning : LogMessageType.Ok;
            Log.AddLog(mesType, String.Format("Workflow completed: {0} successful, {1} failed",
                                              context.CompletedCount, context.FailedCount));
            this.State = EntityExecutingState.Completed;
        }
        
        /// <summary>
        /// Executes provided Task
        /// </summary>
        /// <param name="task">Task to execute</param>
        /// <param name="context">Common data for execution</param>
        public void RunTask(Task task, WorkflowExecutionContext context)
        {
            if (task.IsActive)
            {
                if (task.State == TaskExecutingState.Completed)
                    Log.AddLog(LogMessageType.Info, String.Format("Task '{0}' is already completed", task.CommandText));
                else if (task.State != TaskExecutingState.Executing)
                {
                    try
                    {
                        if (task is ITaskExecutable)
                        {
                            Log.AddLog(LogMessageType.Run, String.Format("Executing '{0}'", task.CommandText));
                            task.State = TaskExecutingState.Executing;
                            DoEvents.Push();
                            (task as ITaskExecutable).Execute(context);
                            task.State = TaskExecutingState.Completed;
                            context.CompletedCount++;
                            Log.AddLog(LogMessageType.Ok, String.Format("Task '{0}' completed", task.CommandText));
                            DoEvents.Push();
                        }
                        else
                            Log.AddLog(LogMessageType.Info, String.Format("Task '{0}' is not executable", task.CommandText));
                    }
                    catch (TaskConditionException ex)
                    {
                        task.State = TaskExecutingState.NotExecuted;
                        Log.AddLog(LogMessageType.Info, String.Format("Task '{0}' doesn't match the condition: {1}", task.CommandText, ex.Message));
                    }
                    catch (TaskExecutionException ex)
                    {
                        task.State = TaskExecutingState.Failed;
                        context.FailedCount++;
                        this.State = EntityExecutingState.Error;
                        Log.AddLog(LogMessageType.Error, String.Format("Exception at {0} ('{1}'): {2}",
                                                                       task.GetType().ToString(), task.CommandText, ex.Message));
                    }

                    if (context.RunDependencies && task.State != TaskExecutingState.Failed &&
                        task.State != TaskExecutingState.NotExecuted)
                        foreach (Task dependency in task.DependentTasks)
                            RunTask(dependency, context);
                }
            }
        }

        public void Reset()
        {
            foreach (Task task in this.Tasks)
                task.ResetState();
            this.State = EntityExecutingState.Ready;
        }
        
        /// <summary>
        /// Recursive method that serializes a Task with all dependencies into XML
        /// </summary>
        /// <param name="task">Task to be serialized</param>
        /// <returns>XElement that contains serialized info</returns>
        private XElement SerializeTask(Task task)
        {
            XElement result = new XElement(XmlValidation.WorkflowNamespace + "Task",
                                           new XAttribute("Type", task.GetType().AssemblyQualifiedName),
                                           new XAttribute("IsActive", task.IsActive),
                                           new XElement(XmlValidation.WorkflowNamespace + "TaskAttributes",
                                                        task.Attributes.Select(t =>
                                                                               new XElement(XmlValidation.WorkflowNamespace + "TaskAttribute",
                                                                                            new XAttribute("Name", t.Key),
                                                                                            new XAttribute("Value", t.Value)
                                                                                           )
                                                                              )
                                                       ),
                                           new XElement(XmlValidation.WorkflowNamespace + "DependentTasks", task.DependentTasks.Select(t => SerializeTask(t)))
                                          );
            
            return result;
        }
        
        /// <summary>
        /// Serializes a workflow into XML
        /// </summary>
        /// <returns>XDocument with serialized workflow</returns>
        public override XDocument Serialize()
        {
            this.XML = new XDocument(
                new XDeclaration("1.0", "utf-8", "yes"),
                new XComment(String.Format("{0}{1}", this.FileFormatCommentString, XmlValidation.CurrentFormatVersion)),
                new XElement(XmlValidation.WorkflowNamespace + "Workflow",
                             new XAttribute("Id", this.Id.ToString()),
                             new XElement(XmlValidation.WorkflowNamespace + "Tasks", Tasks.Select(t => SerializeTask(t))),
                             new XElement(XmlValidation.WorkflowNamespace + "Variables")
                            )
               );
            
            return this.XML;
        }
        
        private void DeserializeTask(XElement element, Task parentTask)
        {
            if (element.Name.LocalName.Equals("Task"))
            {
                Task result = TaskManager.GetTaskFromAssemblyQualifiedName(element.Attribute("Type").Value);
                try
                {
                    result.IsActive = Convert.ToBoolean(element.Attribute("IsActive").Value);
                    IEnumerable<TaskAttributeDefinition> attrs = element.Element(XmlValidation.WorkflowNamespace + "TaskAttributes").Elements(XmlValidation.WorkflowNamespace + "TaskAttribute").Select(
                        a => new TaskAttributeDefinition() { Key = a.Attribute("Name").Value, Value = a.Attribute("Value").Value });
                    foreach (TaskAttributeDefinition attr in attrs)
                        result.Attributes[attr.Key].Value = attr.Value;
                    
                    if (parentTask == null)
                        this.Tasks.Add(result);
                    else
                        parentTask.AddDependency(result);
                }
                catch (Exception ex)
                {
                    this.Log.AddLog(LogMessageType.Error, String.Format("Exception at '{0}': {1}", result.GetType().ToString(), ex.Message));
                }
                
                foreach (XElement child in element.Element(XmlValidation.WorkflowNamespace + "DependentTasks").Elements())
                    DeserializeTask(child, result);
            }
        }
        
        /// <summary>
        /// Loads workflow from a file
        /// </summary>
        protected override void Deserialize()
        {
            base.Deserialize();
            try
            {
                foreach (XElement element in this.XML.Element(XmlValidation.WorkflowNamespace + "Workflow").Element(XmlValidation.WorkflowNamespace + "Tasks").Elements())
                    DeserializeTask(element, null);
            }
            catch (Exception ex)
            {
                this.Log.AddLog(LogMessageType.Error, String.Format("Failed to load workflow: {0}", ex.Message));
            }
        }
        
        /// <summary>
        /// Resets the internal counter for new workflows, i.e. next workflow will be Workflow1 again
        /// </summary>
        public static void ResetInternalIndex()
        {
            _workflowIndex = 0;
        }
        
        #endregion
    }
}
